#include <stdio.h>
#include "nachossyscalls.h"
#include "system.h"
#include "processmanager.h"
#include "sysopenfile.h"
#include "openuserfile.h"


void dummyForkFunc(int userFuncPtr)
{	
	AddrSpace *space = currentThread->space;

	// Restore any necessary data
	currentThread->RestoreUserState();
    space->RestoreState();	

	// Set the PC registers
    machine->WriteRegister(PrevPCReg,  userFuncPtr-4);    
	machine->WriteRegister(PCReg,  userFuncPtr);	
    machine->WriteRegister(NextPCReg,  userFuncPtr+4);

	// Execute
	machine->Run();
}

void Fork(void (*func)())
{

	AddrSpace *currentSpace, *duplicateSpace;

	// Get the parent PID
    processManagerLock->Acquire();
	int parentPID = processManager->findPIDByThread(currentThread);
    processManagerLock->Release();


	// Set up the thread
	Thread *forkedProcess = new Thread("forked thread");
	// Create the PCB for the forked process
    processManagerLock->Acquire();
    ProcessCB *pcb = processManager->constructPCB(forkedProcess, parentPID);
    processManagerLock->Release();
	// Duplicate the current address space
	addrSpaceLock->Acquire();
	currentSpace = currentThread->space;
	duplicateSpace = currentSpace->duplicate(pcb->getPID());
    addrSpaceLock->Release();

	// Check if allocation failed
	if(duplicateSpace == NULL)
	{
		printf("Process [%d] Fork: start at address [0x%x] with [%d] pages memory failed\n", parentPID, ((int)func), currentSpace->getPagesRequired());
		return;
	}

	// Attach the address space to the new processes's thread
	forkedProcess->space = duplicateSpace;

	printf("Process [%d] Fork: start at address [0x%x] with [%d] pages memory\n",  parentPID, ((int)func), duplicateSpace->getPagesRequired());

	// Save necessary data until the child actually gets run
    addrSpaceLock->Acquire();
	currentSpace->SaveState();
	forkedProcess->SaveUserState();
    addrSpaceLock->Release();
	// Set the child process as ready to run by calling fork	
	forkedProcess->Fork(dummyForkFunc, (int)func);
}

void SysCallExit(int status)
{
    processManagerLock->Acquire();
    int pid = processManager->findPIDByThread(currentThread);
    processManager->processFinished(pid, status);
    processManagerLock->Release();
    printf("Process [%d] exits with [%d]\n", pid, status);
    currentThread->Finish();
}

void Yield()
{
	currentThread->Yield();
}

int Exec(int fileNameAddress)
{
	AddrSpace *currentSpace, *newSpace;
	Thread *newThread;
    processManagerLock->Acquire();
	int parentPID = processManager->findPIDByThread(currentThread);
    processManagerLock->Release();
	
	// Get the current address space
    addrSpaceLock->Acquire();
    currentSpace = currentThread->space;
    addrSpaceLock->Release();

	// Get the file name
	int maxFileNameLength = 128;
	char *fileName = new char[maxFileNameLength];
	bool success = getUserString(fileName, maxFileNameLength, fileNameAddress);
	if(!success)
	{	
		printf("Exec Program: [%d] loading [Bad File Name Pointer] failed\n", parentPID);
		delete fileName;
		return -1;
	}
		
	// We should have a file name at this point; it might be truncated, but we will double check for problems opening it
    fileLock->Acquire();
    OpenFile *executable = fileSystem->Open(fileName);
    fileLock->Release();
    if(executable == NULL)
	{
		// Unable to open file
		printf("Exec Program: [%d] loading [%s] failed\n", parentPID, fileName);
		delete fileName;
		return -1;
    }

	// Create a new thread
	newThread = new Thread("exex'd thread");
		
	// Construct the PCB
    processManagerLock->Acquire();
	ProcessCB *pcb = processManager->constructPCB(newThread, parentPID); 
    processManagerLock->Release();

	// Create the address space
    addrSpaceLock->Acquire();
	newSpace = new AddrSpace(executable, pcb->getPID());
    addrSpaceLock->Release();
	if(!newSpace->isCreated())
	{
		delete newSpace;
		printf("Exec Program: [%d] loading [%s] failed\n", parentPID, fileName);
		delete fileName;
		delete executable;
		return -1;
	}

	// Attach the new address space to the thread
	newThread->space = newSpace;

	// Initialize the registers of the address space
    addrSpaceLock->Acquire();
	newSpace->InitRegisters();
    addrSpaceLock->Release();


	// Close the file after usage
	delete executable;

	// Check if loading the executable failed
	if(!success)
	{
		delete newSpace;
		printf("Exec Program: [%d] loading [%s] failed\n", parentPID, fileName);
		delete fileName;
		return -1;
	}

	// Save data in preparation to execute the newly created process
    addrSpaceLock->Acquire();
    currentSpace->SaveState();
	addrSpaceLock->Release();
	newThread->SaveUserState();
	// Set the child process as ready to run by calling fork	
	newThread->Fork(dummyForkFunc, 0);
	printf("Exec Program: [%d] loading [%s]\n", parentPID, fileName);
	delete fileName;
	return pcb->getPID();
}

int Join(int waitOnPID)
{
    processManagerLock->Acquire();
	int currentPID = processManager->findPIDByThread(currentThread);
    processManagerLock->Release();
    int result = processManager->waitOn(currentPID, waitOnPID); // If waitOn was in the processManagerLock it would greate a deadlock
    return result;
}

/*
	Helper function
	Reads a string from the user space
	Takes as parameters a buffer for the string, the size of the buffer, and the
	location of the string in the userspace.
	Returns a bool indicating whether the operation was successful or not
*/
bool getUserString(char* buffer, int bufferSize, int stringLocation)
{
	int offset = 0;
	AddrSpace *currentSpace = currentThread->space;


	// Read characters from the user space one at a time
	while(true)
	{
		// Get the physical address of the next character
		int vAddr = stringLocation + offset;
		int pAddr = -1;
		bool validAddress = true;
        	addrSpaceLock->Acquire();
		
		validAddress = currentSpace->translateVM(vAddr, &pAddr);
		addrSpaceLock->Release();
		if(!validAddress)
		{
			// Error - bad address
			return false;
		}
	
		// Read and store the next character
                machineLock->Acquire();
		char readChar = machine->mainMemory[pAddr];
                machineLock->Release();
                buffer[offset] = readChar;
		
		// If it was a null char, stop reading
		if(readChar == '\0')
			break;
	
		// Increment the pointer and make sure we don't overflow
		offset++;
		if(offset == bufferSize)
		{
			// Fix the string by adding the null character to it and stop reading
			buffer[bufferSize-1] = '\0';
			break;
		}
	}
	return true;
}

/*
	System call for creating a file.
	Takes the pointer to a string in userspace as the file name.
	Not guaranteed to succeed if the file name is invalid
*/
void Create(int fileNameAddress)
{
	int maxFileNameLength = 128;
	char *fileName = new char[maxFileNameLength];
	bool success = getUserString(fileName, maxFileNameLength, fileNameAddress);
	if(!success)
	{
		delete fileName;
		return;
	}

    fileLock->Acquire();
	success = fileSystem->Create(fileName, 0);
    fileLock->Release();

    delete fileName;
}

/*
	System call for opening a file.
	Takes the pointer to a string in userspace as the file name.
	Returns the opened file's ID in the user file table, or -1 on failure
*/
int Open(int fileNameAddress)
{
	int maxFileNameLength = 128;
	char *fileName = new char[maxFileNameLength];
	bool success = getUserString(fileName, maxFileNameLength, fileNameAddress);
	if(!success)
	{
		delete fileName;
		return -1;
	}

        sysFileLock->Acquire();
	int sysOpenFileID = getSysOpenFile(fileName);
        sysFileLock->Release();
	// Check if the sysOpenFile object was loaded/created successfully
	if(sysOpenFileID == -1)
	{
		delete fileName;
		return -1;  // Something failed
	}

	// Create the user open file object
	OpenUserFile *file = new OpenUserFile(0, fileName, sysOpenFileID);

    // Attach it to the current address space
	int openUserFileID = currentThread->space->addOpenUserFile(file);	

	delete fileName;
	return openUserFileID;
}


/*
	Helper function to return to the Open system call
	Takes a filename and attempts to locate it in the sys open file table.
	Returns the corresponding user open file ID (location in table) on success.
	Creates the sys open file object if necessary. 
	Returns -1 on failure (e.g. file does not exist)
*/
int getSysOpenFile(char *fileName)
{
	int sysOpenFileID = -1;	// Set to the sentinel value -1 (not found) for now
	int freeIndex = -1;  	// ""
	
	// Check if it is already open somewhere
	// At the same time we are checking, look for free spots in case we need to
	// create the sys open file object.
        sysFileLock->Acquire();
	for(int i=0; i<sysOpenFileList->size(); i++)
	{
		if(sysOpenFileList->at(i) == NULL)
		{
			freeIndex = i;			
			continue;
		}
		// Check for a file name match
		if( strcmp( sysOpenFileList->at(i)->getFileName() , fileName ) == 0 )
		{
			sysOpenFileID = i;
			break;
		}
	}


	// File was not open anywhere, create the sys file object
	if(sysOpenFileID == -1)
	{
		OpenFile *file = fileSystem->Open(fileName);
		// Check if we opened the file successfully
		if(file == NULL)
                {
                    sysFileLock->Release();
                    return -1;	// Failed to open the file
                }
		// Use a pre-found free location if we have one
		if(freeIndex != -1)
		{
			// TODO -1 ... is a temporary value, I do not know what it is for
			sysOpenFileList->at(freeIndex) = new SysOpenFile(file, -1, fileName);
                        sysFileLock->Release();
                        return freeIndex;
		}
		else
		{	
			// TODO -1 ... is a temporary value, I do not know what it is for		
			sysOpenFileList->push_back(new SysOpenFile(file, -1, fileName));
                        sysFileLock->Release();
                        return (sysOpenFileList->size() - 1);
		}
	}
	// File was already open somewhere
	else
	{
            sysFileLock->Release();
            return sysOpenFileID;
	}
        sysFileLock->Release();
        return -1;
}

void myWrite(int bufferLoc, int size, int openFileID)
{
    addrSpaceLock->Acquire();

	AddrSpace *space = currentThread->space;
	bool success = space->write(bufferLoc, size, openFileID);
	if(!success)
		DEBUG('a', "Failed to perform user write of size %d to file id %d \n", size, openFileID);
	addrSpaceLock->Release();

}

int myRead(int bufferLoc, int size, int openFileID)
{
    addrSpaceLock->Acquire();
	AddrSpace *space = currentThread->space;
	int result = space->read(bufferLoc, size, openFileID);

    addrSpaceLock->Release();
	return result;
}

void myClose(int openFileID)
{
    addrSpaceLock->Acquire();
	AddrSpace *space = currentThread->space;
	space->closeUserFile(openFileID);
    addrSpaceLock->Release();
}



